﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Aeon.Emulator.Instructions;

namespace Aeon.Emulator.Decoding
{
    /// <summary>
    /// Describes the format of an instruction.
    /// </summary>
    internal struct InstructionInfo
    {
        #region Constructors
        /// <summary>
        /// Initializes static fields in the InstructionInfo struct.
        /// </summary>
        static InstructionInfo()
        {
            var fields = typeof(OperandType).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
            foreach(var field in fields)
            {
                object[] customAttributes = field.GetCustomAttributes(typeof(OperandStringAttribute), false);
                if(customAttributes != null && customAttributes.Length == 1)
                {
                    var operandStringAttribute = (OperandStringAttribute)customAttributes[0];
                    operandStrings.Add(operandStringAttribute.OperandString, (OperandType)field.GetValue(null));
                }
            }

        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets or sets the instruction opcode.
        /// </summary>
        public ushort Opcode
        {
            get { return opcode; }
            set { opcode = value; }
        }
        public OperandFormat Operands
        {
            get { return format; }
            set { format = value; }
        }
        /// <summary>
        /// Gets or sets information about the ModR/M byte.
        /// </summary>
        public ModRmInfo ModRmByte
        {
            get { return rmInfo; }
            set { rmInfo = value; }
        }
        /// <summary>
        /// Gets or sets the extended opcode information in the ModR/M byte.
        /// </summary>
        public byte ExtendedRmOpcode
        {
            get { return rmOpcode; }
            set { rmOpcode = value; }
        }
        /// <summary>
        /// Gets the index of the operand which corresponds to a register+ instruction.
        /// </summary>
        public int RPlusIndex
        {
            get
            {
                if(ModRmByte == ModRmInfo.RegisterPlus)
                    return this.Operands.IndexOfAny(new[] { OperandType.RegisterST, OperandType.RegisterByte, OperandType.RegisterWord });

                return -1;
            }
        }
        /// <summary>
        /// Gets or sets the descriptor for the method which performs the emulation of this opcode.
        /// </summary>
        public System.Reflection.MethodInfo[] EmulateMethods
        {
            get { return emulateMethod; }
            set { emulateMethod = value; }
        }
        public Decoding.DecodeAndEmulate[] NewEmulators
        {
            get { return newEmulators; }
            set { newEmulators = value; }
        }
        /// <summary>
        /// Gets or sets the display name of the instruction.
        /// </summary>
        public string Name
        {
            get { return instructionName; }
            set { instructionName = value; }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this is a prefix instruction.
        /// </summary>
        public bool IsPrefix
        {
            get { return isPrefix; }
            set { isPrefix = value; }
        }
        /// <summary>
        /// Gets or sets a value indicating whether the instruction is a multi-byte opcode.
        /// </summary>
        public bool IsMultiByte
        {
            get { return this.multiByte; }
            set { this.multiByte = value; }
        }
        /// <summary>
        /// Gets a value indicating whether the instruction would be affected by a change in the operand size.
        /// </summary>
        public bool IsAffectedByOperandSize
        {
            get
            {
                return this.Operands.IndexOfAny(new[] { OperandType.ImmediateFarPointer, OperandType.ImmediateRelativeWord, OperandType.ImmediateWord, OperandType.IndirectFarPointer, OperandType.MemoryOffsetWord, OperandType.RegisterOrMemoryWord, OperandType.RegisterOrMemoryWordNearPointer, OperandType.RegisterWord, OperandType.EffectiveAddress }) >= 0;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the instruction would be affected by a change in the address size.
        /// </summary>
        public bool IsAffectedByAddressSize
        {
            get
            {
                return this.Operands.IndexOfAny(new[] { OperandType.IndirectFarPointer, OperandType.MemoryInt16, OperandType.MemoryInt32, OperandType.MemoryFloat32, OperandType.MemoryFloat64, OperandType.MemoryFloat80, OperandType.MemoryInt64, OperandType.MemoryOffsetByte, OperandType.MemoryOffsetWord, OperandType.RegisterOrMemory16, OperandType.RegisterOrMemory32, OperandType.RegisterOrMemoryByte, OperandType.RegisterOrMemoryWord, OperandType.RegisterOrMemoryWordNearPointer, OperandType.EffectiveAddress }) >= 0;
            }
        }
        /// <summary>
        /// Gets or sets the actions to take in the instruction epilog.
        /// </summary>
        public ClearInEpilog Epilog
        {
            get { return this.clearInEpilog; }
            set { this.clearInEpilog = value; }
        }
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Converts a string representation of the instruction to an InstructionInfo struct.
        /// </summary>
        /// <param name="instructionFormat">String representation of the instruction format.</param>
        /// <returns>InstructionInfo struct generated from the instruction format string.</returns>
        public static InstructionInfo Parse(string instructionFormat)
        {
            InstructionInfo info = new InstructionInfo();

            string[] outer = instructionFormat.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            
            string[] innerCode;
            if(!outer[0].Contains('+'))
                innerCode = outer[0].Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            else
                innerCode = new string[2] { outer[0].TrimEnd('+'), "+" };
            info.Opcode = ushort.Parse(innerCode[0], System.Globalization.NumberStyles.HexNumber);
            if(info.Opcode > 0xFF)
            {
                info.Opcode = (ushort)(((info.Opcode & 0xFF) << 8) | ((info.Opcode >> 8) & 0xFF));
                info.IsMultiByte = true;
            }

            if(innerCode.Length > 1)
            {
                if(innerCode[1][0] == 'r')
                    info.ModRmByte = ModRmInfo.All;
                else if(innerCode[1][0] == '+')
                    info.ModRmByte = ModRmInfo.RegisterPlus;
                else
                {
                    info.ModRmByte = ModRmInfo.OnlyRm;
                    info.ExtendedRmOpcode = byte.Parse(new string(innerCode[1][0], 1));
                }
            }

            if(outer.Length > 1)
            {
                string[] innerOperands = outer[1].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                int operandCode = 0;

                operandCode = (int)ParseOperand(innerOperands[0]);

                if(innerOperands.Length > 1)
                {
                    operandCode |= (int)ParseOperand(innerOperands[1]) << 8;

                    if(innerOperands.Length > 2)
                        operandCode |= (int)ParseOperand(innerOperands[2]) << 16;
                }

                OperandFormat format;
                if(!operandFormats.TryGetValue(operandCode, out format))
                {
                    format = new OperandFormat(operandCode);
                    operandFormats.Add(operandCode, format);
                }

                info.format = format;
            }
            else
            {
                OperandFormat format;
                if(!operandFormats.TryGetValue(0, out format))
                {
                    format = new OperandFormat(0);
                    operandFormats.Add(0, format);
                }

                info.format = format;
            }

            return info;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Splits the instruction info into sub infos if necessary.
        /// </summary>
        /// <returns>Final instruction infos.</returns>
        public IEnumerable<InstructionInfo> Expand()
        {
            if(this.ModRmByte != ModRmInfo.RegisterPlus)
            {
                yield return this;
                yield break;
            }

            for(int r = 0; r < 8; r++)
            {
                InstructionInfo subInst = this;
                subInst.ModRmByte = ModRmInfo.None;
                if(!subInst.IsMultiByte)
                    subInst.Opcode += (ushort)r;
                else
                    subInst.Opcode += (ushort)(r << 8);

                OperandFormat operands;
                int codes = this.format.PackedCode & ~(0xFF << (this.RPlusIndex * 8));
                codes |= (int)GetRegisterIndex(r, this.Operands[this.RPlusIndex]) << (this.RPlusIndex * 8);
                if(!operandFormats.TryGetValue(codes, out operands))
                {
                    operands = new OperandFormat(codes);
                    operandFormats.Add(codes, operands);
                }

                subInst.format = operands;

                yield return subInst;
            }
        }
        /// <summary>
        /// Gets a string representation of the InstructionInfo struct.
        /// </summary>
        /// <returns>String representation of the instruction.</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            if(opcode <= 0xFF)
                sb.Append(opcode.ToString("X2"));
            else
                sb.Append(opcode.ToString("X4"));

            switch(rmInfo)
            {
            case ModRmInfo.All:
                sb.Append("/r");
                break;

            case ModRmInfo.OnlyRm:
                sb.AppendFormat("/{0}", rmOpcode);
                break;
            }

            if(format.Count > 0)
            {
                sb.Append(' ');
                sb.Append(format.ToString());
            }

            return sb.ToString();
        }
        /// <summary>
        /// Tests for equality with another object.
        /// </summary>
        /// <param name="obj">Other object to test.</param>
        /// <returns>True if objects are equal; otherwise false.</returns>
        public override bool Equals(object obj)
        {
            if(obj is InstructionInfo)
            {
                InstructionInfo other = (InstructionInfo)obj;
                return this.opcode == other.opcode && this.format == other.format && this.rmInfo == other.rmInfo && this.rmOpcode == other.rmOpcode;
            }
            else
                return false;
        }
        /// <summary>
        /// Gets a value used as a hash code for the InstructionInfo struct.
        /// </summary>
        /// <returns>Hash code for the InstructionInfo struct.</returns>
        public override int GetHashCode()
        {
            return opcode | ((byte)rmOpcode << 16);
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Converts the shorthand string representation of an operand type to an OperandType value.
        /// </summary>
        /// <param name="s">Shorthand string representation of an operand type.</param>
        /// <returns>OperandType value parsed from the string.</returns>
        private static OperandType ParseOperand(string s)
        {
            return operandStrings[s];
        }
        /// <summary>
        /// Returns a register that corresponds to an index.
        /// </summary>
        /// <param name="index">Index of register.</param>
        /// <param name="operandType">Type of register.</param>
        /// <returns>Register that corresponds to the index.</returns>
        private static OperandType GetRegisterIndex(int index, OperandType operandType)
        {
            if(operandType == OperandType.RegisterByte)
            {
                switch(index)
                {
                case 0:
                    return OperandType.RegisterAL;

                case 1:
                    return OperandType.RegisterCL;

                case 2:
                    return OperandType.RegisterDL;

                case 3:
                    return OperandType.RegisterBL;

                case 4:
                    return OperandType.RegisterAH;

                case 5:
                    return OperandType.RegisterCH;

                case 6:
                    return OperandType.RegisterDH;

                case 7:
                    return OperandType.RegisterBH;
                }
            }
            else if(operandType == OperandType.RegisterWord)
            {
                switch(index)
                {
                case 0:
                    return OperandType.RegisterAX;

                case 1:
                    return OperandType.RegisterCX;

                case 2:
                    return OperandType.RegisterDX;

                case 3:
                    return OperandType.RegisterBX;

                case 4:
                    return OperandType.RegisterSP;

                case 5:
                    return OperandType.RegisterBP;

                case 6:
                    return OperandType.RegisterSI;

                case 7:
                    return OperandType.RegisterDI;
                }
            }
            else if(operandType == OperandType.RegisterST)
            {
                switch(index)
                {
                case 0:
                    return OperandType.RegisterST0;

                case 1:
                    return OperandType.RegisterST1;

                case 2:
                    return OperandType.RegisterST2;

                case 3:
                    return OperandType.RegisterST3;

                case 4:
                    return OperandType.RegisterST4;

                case 5:
                    return OperandType.RegisterST5;

                case 6:
                    return OperandType.RegisterST6;

                case 7:
                    return OperandType.RegisterST7;
                }
            }

            return operandType;
        }
        #endregion

        #region Private Static Fields
        /// <summary>
        /// OperandTypes which correspond to string tokens.
        /// </summary>
        private static readonly Dictionary<string, OperandType> operandStrings = new Dictionary<string, OperandType>();
        /// <summary>
        /// Operand format instance pool.
        /// </summary>
        private static readonly Dictionary<int, OperandFormat> operandFormats = new Dictionary<int, OperandFormat>();
        #endregion

        #region Private Fields
        private ushort opcode;
        private OperandFormat format;
        private ModRmInfo rmInfo;
        private byte rmOpcode;
        private MethodInfo[] emulateMethod;
        private DecodeAndEmulate[] newEmulators;
        private string instructionName;
        private bool isPrefix;
        private bool multiByte;
        private ClearInEpilog clearInEpilog;
        #endregion
    }

    #region ModRmInfo Enum
    /// <summary>
    /// Describes the ModR/M byte of an instruction.
    /// </summary>
    public enum ModRmInfo : byte
    {
        /// <summary>
        /// The ModR/M byte is not present.
        /// </summary>
        None,
        /// <summary>
        /// Only the R/M field in the ModR/M byte is used.
        /// </summary>
        OnlyRm,
        /// <summary>
        /// The entire ModR/M byte is used.
        /// </summary>
        All,
        /// <summary>
        /// A register code is added to the opcode.
        /// </summary>
        RegisterPlus
    }
    #endregion

    #region PrefixFlags Enum
    /// <summary>
    /// Describes any instruction prefixes present.
    /// </summary>
    [Flags]
    [CLSCompliant(false)]
    public enum PrefixFlags : ushort
    {
        /// <summary>
        /// The instruction has no prefixes.
        /// </summary>
        None = 0,
        /// <summary>
        /// The CS segment register should be used.
        /// </summary>
        CSOverride = (1 << 0),
        /// <summary>
        /// The SS segment register should be used.
        /// </summary>
        SSOverride = (1 << 1),
        /// <summary>
        /// The DS segment register should be used.
        /// </summary>
        DSOverride = (1 << 2),
        /// <summary>
        /// The ES segment register should be used.
        /// </summary>
        ESOverride = (1 << 3),
        /// <summary>
        /// The FS segment register should be used.
        /// </summary>
        FSOverride = (1 << 4),
        /// <summary>
        /// The GS segment register should be used.
        /// </summary>
        GSOverride = (1 << 5),
        /// <summary>
        /// Specifies the LOCK prefix.
        /// </summary>
        Lock = (1 << 6),
        /// <summary>
        /// Specifies the REPN prefix for string instructions.
        /// </summary>
        REPN = (1 << 7),
        /// <summary>
        /// Specifies the REP prefix for string instructions.
        /// </summary>
        REP = (1 << 8),
        /// <summary>
        /// Specifies the Operand-Size prefix.
        /// </summary>
        OperandSize = (1 << 9),
        /// <summary>
        /// Specifies the Address-Size prefix.
        /// </summary>
        AddressSize = (1 << 10)
    }
    #endregion
}
